use crate::tutorials::wgpu::{
    camera::*,
    camera_ctrl::*,
    compute::*,
    const_values::*,
    drawer::*,
    hdr::*,
    instance::*,
    light::*,
    model::{self, *},
    texture::{self, *},
    utils::*,
    vertex::*,
};
use ash::{
    Device, Entry, Instance,
    ext::{debug_utils, device_fault},
    vk::{self, Extent2D, Handle, Image},
};
use cgmath::{Deg, Euler, Matrix3, Matrix4, Quaternion, Rad, Vector3, Vector4, prelude::*};
use core::f32;
use image::{EncodableLayout, GenericImageView, load, load_from_memory, open};
use std::{
    cmp::min,
    collections::HashMap,
    ffi::{CStr, CString},
    io::Read,
    ops::Deref,
    os::raw::*,
    sync::{Arc, Mutex, MutexGuard},
    time::*,
};
use wgpu::{
    BindGroupLayoutDescriptor, Color, ComputePassDescriptor, Operations, RenderPassColorAttachment,
    ShaderStages, include_wgsl,
    util::{BufferInitDescriptor, DeviceExt},
    wgt::{CommandEncoderDescriptor, TextureViewDescriptor},
};
use winit::{
    application::ApplicationHandler,
    dpi::PhysicalSize,
    event::{self, ElementState, Event, WindowEvent},
    event_loop::{ActiveEventLoop, ControlFlow, EventLoop, EventLoopBuilder},
    keyboard::{KeyCode, *},
    window::{Window, WindowAttributes, WindowId},
};

pub struct State<'a> {
    pub window: Arc<Window>,
    pub surface: wgpu::Surface<'a>,
    pub device: wgpu::Device,
    pub queue: wgpu::Queue,
    pub config: wgpu::SurfaceConfiguration,
    pub size: winit::dpi::PhysicalSize<u32>,

    pub render_pipeline: wgpu::RenderPipeline,
    pub light_render_pipeline: wgpu::RenderPipeline,

    pub compute_pipeline: wgpu::ComputePipeline,
    pub compute_bind_grp_layout: wgpu::BindGroupLayout,
    pub compute_bind_grp: wgpu::BindGroup,
    pub grid_size: (u32, u32, u32),
    pub workgroup_size: (u32, u32, u32),
    pub workgroup_count: (u32, u32, u32),
    pub compute_texture: texture::Texture,
    pub compute_uniform: ComputeUniform,
    pub compute_uniform_buffer: wgpu::Buffer,
    // pub compute_data: ComputeData,
    // pub compute_data_buffer: wgpu::Buffer,
    pub present_depth_texture: texture::Texture,
    pub present_render_pipeline: wgpu::RenderPipeline,
    pub present_bind_group_layout: wgpu::BindGroupLayout,
    pub present_bind_group: wgpu::BindGroup,
    pub present_vertex_buffer: wgpu::Buffer,
    pub present_index_buffer: wgpu::Buffer,

    pub hdr_pipeline: HdrPipeline,
    pub environment_bind_group: wgpu::BindGroup,
    pub sky_pipeline: wgpu::RenderPipeline,

    // pub vertex_buffer: wgpu::Buffer,
    // pub num_verts: u32,
    // pub index_buffer: wgpu::Buffer,
    // pub num_indices: u32,
    pub obj_model: Model,
    pub instances: Vec<super::instance::Instance>,
    pub instance_buffer: wgpu::Buffer,

    pub offscreen_texture: texture::Texture,
    pub depth_texture: texture::Texture,
    // pub diffuse_bind_group: wgpu::BindGroup,
    // pub diffuse_texture: texture::Texture,
    pub camera: Camera,
    pub camera_uniform: CameraUniform,
    pub camera_buffer: wgpu::Buffer,
    pub camera_bind_group: wgpu::BindGroup,
    pub camera_controller: CameraController,

    pub light_uniform: LightUniform,
    pub light_buffer: wgpu::Buffer,
    pub light_bind_group: wgpu::BindGroup,
}

impl<'a> State<'a> {
    // 某些 wgpu 类型需要使用异步代码才能创建
    pub async fn new(event_loop: &ActiveEventLoop, attrs: WindowAttributes) -> Self {
        // device resource
        let window = Arc::new(event_loop.create_window(attrs).unwrap());
        let size = window.inner_size();
        let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor::default());
        let surface = unsafe { instance.create_surface(window.clone()).unwrap() };
        let adapter = instance
            .request_adapter(&wgpu::RequestAdapterOptions {
                power_preference: wgpu::PowerPreference::default(),
                compatible_surface: Some(&surface),
                force_fallback_adapter: false,
            })
            .await
            .unwrap();
        // let adapter = instance
        //     .enumerate_adapters(wgpu::Backends::all())
        //     .into_iter().filter(|adapter| { // 检查该适配器是否支持我们的 surface
        //         adapter.is_surface_supported(&surface)
        //     })
        //     .next()
        //     .unwrap();
        let (device, queue) = adapter
            .request_device(&wgpu::DeviceDescriptor {
                label: None,
                required_features: wgpu::Features::empty(), // wgpu::Features::all_webgpu_mask(),
                required_limits: wgpu::Limits::default(),   // wgpu::Limits::downlevel_defaults(),
                memory_hints: wgpu::MemoryHints::Performance,
                trace: wgpu::Trace::Off,
            })
            .await
            .unwrap();
        let config = surface
            .get_default_config(&adapter, size.width, size.height)
            .unwrap();
        surface.configure(&device, &config);

        // vertex data
        // let vertex_buffer = device.create_buffer_init(
        //     &wgpu::util::BufferInitDescriptor {
        //         label: Some("Vertex Buffer"),
        //         contents: bytemuck::cast_slice(VERTICES),
        //         usage: wgpu::BufferUsages::VERTEX,
        //     }
        // );
        // let num_verts = VERTICES.len() as u32;
        // let index_buffer = device.create_buffer_init(
        //     &wgpu::util::BufferInitDescriptor {
        //         label: Some("Index Buffer"),
        //         contents: bytemuck::cast_slice(INDICES),
        //         usage: wgpu::BufferUsages::INDEX,
        //     }
        // );
        // let num_indices = INDICES.len() as u32;

        const SPACE_BETWEEN: f32 = 3.0;
        let instances = (0..NUM_INSTANCES_PER_ROW)
            .flat_map(|z| {
                (0..NUM_INSTANCES_PER_ROW).map(move |x| {
                    let x = SPACE_BETWEEN * (x as f32 - NUM_INSTANCES_PER_ROW as f32 / 2.0);
                    let z = SPACE_BETWEEN * (z as f32 - NUM_INSTANCES_PER_ROW as f32 / 2.0);

                    let position = cgmath::Vector3 { x, y: 0.0, z };
                    let rotation = if position.is_zero() {
                        cgmath::Quaternion::from_axis_angle(
                            cgmath::Vector3::unit_z(),
                            cgmath::Deg(0.0),
                        )
                    } else {
                        cgmath::Quaternion::from_axis_angle(position.normalize(), cgmath::Deg(45.0))
                    };
                    super::instance::Instance { position, rotation }
                })
            })
            .collect::<Vec<_>>();
        let instance_data = instances
            .iter()
            .map(super::instance::Instance::to_raw)
            .collect::<Vec<_>>();
        let instance_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
            label: Some("Instance Buffer"),
            contents: bytemuck::cast_slice(&instance_data),
            usage: wgpu::BufferUsages::VERTEX,
        });

        // depth-tex
        let depth_texture =
            texture::Texture::create_depth_texture(&device, &config, "depth_texture");
        let offscreen_texture = texture::Texture::create_offscreen_texture(
            &device,
            "offscreen_texture",
            (config.width, config.height),
        );

        // let img_path = build_path(IMG_BASE, IMG_NAME);
        // let diffuse_texture =
        //     texture::Texture::from_path(&device, &queue, &img_path, "diffuse_texture", false).unwrap();
        let texture_bind_group_layout = Material::get_default_bind_group_layout(&device);
        // let diffuse_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
        //     layout: &texture_bind_group_layout,
        //     entries: &[
        //         wgpu::BindGroupEntry {
        //             binding: 0,
        //             resource: wgpu::BindingResource::TextureView(&diffuse_texture.view),
        //         },
        //         wgpu::BindGroupEntry {
        //             binding: 1,
        //             resource: wgpu::BindingResource::Sampler(&diffuse_texture.sampler),
        //         },
        //     ],
        //     label: Some("diffuse_bind_group"),
        // });
        // obj model (mesh, material)
        let model_name = "md1.obj";
        let res_dir = std::path::Path::new(env!("OUT_DIR")).join("models");
        println!("res_dir: {:?}", res_dir);
        let obj_model = model::Model::load(
            &device,
            &queue,
            &texture_bind_group_layout,
            res_dir.join(model_name),
        );
        if let Err(e) = obj_model {
            panic!("{e}");
        }
        let obj_model = obj_model.unwrap();

        // camera data
        let camera = Camera {
            // 将相机向上移动 1 个单位，向后移动 2 个单位
            // +z 对应屏幕外侧方向
            eye: (0.0, 1.0, 2.0).into(),
            target: (0.0, 0.0, 0.0).into(),
            up: glam::Vec3::Y,
            aspect: config.width as f32 / config.height as f32,
            fovy: 45.0,
            znear: 0.1,
            zfar: 100.0,
        };
        let camera_controller = CameraController::new(0.2);
        let mut camera_uniform = CameraUniform::new();
        camera_uniform.update_view_proj(&camera);
        let camera_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
            label: Some("Camera Uniform Buffer"),
            contents: bytemuck::cast_slice(&[camera_uniform]),
            usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
        });
        let camera_bind_group_layout =
            device.create_bind_group_layout(&BindGroupLayoutDescriptor {
                label: Some("camera_bind_group_layout"),
                entries: &[wgpu::BindGroupLayoutEntry {
                    binding: 0,
                    visibility: ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
                    ty: wgpu::BindingType::Buffer {
                        ty: wgpu::BufferBindingType::Uniform,
                        has_dynamic_offset: false,
                        min_binding_size: None,
                    },
                    count: None,
                }],
            });
        let camera_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
            layout: &camera_bind_group_layout,
            entries: &[wgpu::BindGroupEntry {
                binding: 0,
                resource: camera_buffer.as_entire_binding(),
            }],
            label: Some("camera_bind_group"),
        });
        // light source
        let mut light_uniform = LightUniform::default();
        light_uniform.position = [2.0, 2.0, 2.0];
        light_uniform.color = [1.0, 1.0, 1.0];

        let light_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
            label: Some("Light VB"),
            contents: bytemuck::cast_slice(&[light_uniform]),
            usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, // 希望能更新光源位置COPY_DST
        });
        let light_bind_group_layout =
            device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
                entries: &[wgpu::BindGroupLayoutEntry {
                    binding: 0,
                    visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
                    ty: wgpu::BindingType::Buffer {
                        ty: wgpu::BufferBindingType::Uniform,
                        has_dynamic_offset: false,
                        min_binding_size: None,
                    },
                    count: None,
                }],
                label: None,
            });
        let light_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
            layout: &light_bind_group_layout,
            entries: &[wgpu::BindGroupEntry {
                binding: 0,
                resource: light_buffer.as_entire_binding(),
            }],
            label: None,
        });

        // render pipeline
        // let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
        //     label: Some("Shader"),
        //     source: wgpu::ShaderSource::Wgsl(include_str!("..\\..\\shaders\\shader.wgsl").into()),
        // });
        // let shader = device.create_shader_module(include_wgsl!("..\\..\\shaders\\shader.wgsl"));
        let render_pipeline_layout =
            device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
                label: Some("Render Pipeline Layout"),
                bind_group_layouts: &[
                    &texture_bind_group_layout,
                    &camera_bind_group_layout,
                    &light_bind_group_layout,
                ],
                push_constant_ranges: &[],
            });
        let render_pipeline = {
            let shader = wgpu::ShaderModuleDescriptor {
                label: Some("Normal Shader"),
                source: wgpu::ShaderSource::Wgsl(
                    include_str!("..\\..\\shaders\\shader.wgsl").into(),
                ),
            };
            create_render_pipeline(
                &device,
                &render_pipeline_layout,
                // config.format,
                offscreen_texture.texture.format(),
                Some(texture::Texture::DEPTH_FORMAT),
                &[model::ModelVertex::desc(), InstanceRaw::desc()],
                wgpu::PrimitiveTopology::TriangleList,
                shader,
            )
        };

        let light_render_pipeline = {
            let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
                label: Some("Light Pipeline Layout"),
                bind_group_layouts: &[&camera_bind_group_layout, &light_bind_group_layout],
                push_constant_ranges: &[],
            });
            let shader = wgpu::ShaderModuleDescriptor {
                label: Some("Light Shader"),
                source: wgpu::ShaderSource::Wgsl(
                    include_str!("..\\..\\shaders\\light.wgsl").into(),
                ),
            };
            create_render_pipeline(
                &device,
                &layout,
                // config.format,
                offscreen_texture.texture.format(),
                Some(texture::Texture::DEPTH_FORMAT),
                &[model::ModelVertex::desc()],
                wgpu::PrimitiveTopology::TriangleList,
                shader,
            )
        };

        // compute pipeline
        let grid_sz: (u32, u32, u32) = (config.width, config.height, 1);
        let work_grp_sz: (u32, u32, u32) = DEFAULT_WORKGROUP_SIZE;
        let work_grp_cnt: (u32, u32, u32) = (
            (grid_sz.0 + (work_grp_sz.0 - 1)) / work_grp_sz.0,
            (grid_sz.1 + (work_grp_sz.1 - 1)) / work_grp_sz.1,
            (grid_sz.2 + (work_grp_sz.2 - 1)) / work_grp_sz.2,
        );

        let compute_uniform = ComputeUniform {
            img_size: [config.width, config.height],
            uv_offset: [1, 1],
        };
        let compute_uniform_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
            usage: wgpu::BufferUsages::UNIFORM
                | wgpu::BufferUsages::COPY_DST
                | wgpu::BufferUsages::STORAGE,
            label: Some("Compute Uniform Buffer"),
            contents: bytemuck::cast_slice(&[compute_uniform]),
        });
        // let compute_data = ComputeData {
        // };
        // let compute_data_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
        //     // VERTEX | STORAGE 表示此缓冲区可以做为顶点缓冲区以及存储缓冲区来使用
        //     usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::STORAGE,
        //     label: Some("Compute Data Buffer"),
        //     contents: bytemuck::cast_slice(&[compute_data]),
        // });
        let compute_texture = texture::Texture::create_compute_texture(
            &device,
            "Compute Texture",
            (config.width, config.height),
        );
        let compute_bind_grp_layout: wgpu::BindGroupLayout =
            device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
                entries: &[
                    wgpu::BindGroupLayoutEntry {
                        binding: 0,
                        visibility: wgpu::ShaderStages::COMPUTE,
                        ty: wgpu::BindingType::Buffer {
                            ty: wgpu::BufferBindingType::Uniform,
                            has_dynamic_offset: false,
                            min_binding_size: None,
                        },
                        count: None,
                    },
                    wgpu::BindGroupLayoutEntry {
                        binding: 1,
                        visibility: wgpu::ShaderStages::COMPUTE,
                        ty: wgpu::BindingType::Texture {
                            multisampled: false,
                            view_dimension: wgpu::TextureViewDimension::D2,
                            sample_type: wgpu::TextureSampleType::Float { filterable: true },
                        },
                        count: None,
                    },
                    wgpu::BindGroupLayoutEntry {
                        binding: 2,
                        visibility: wgpu::ShaderStages::COMPUTE,
                        ty: wgpu::BindingType::StorageTexture {
                            access: wgpu::StorageTextureAccess::WriteOnly,
                            format: wgpu::TextureFormat::Rgba8Unorm,
                            view_dimension: wgpu::TextureViewDimension::D2,
                        },
                        count: None,
                    },
                ],
                label: Some("compute_bind_group_layout"),
            });
        let compute_bind_grp = device.create_bind_group(&wgpu::BindGroupDescriptor {
            layout: &compute_bind_grp_layout,
            entries: &[
                wgpu::BindGroupEntry {
                    binding: 0,
                    resource: compute_uniform_buffer.as_entire_binding(),
                },
                wgpu::BindGroupEntry {
                    binding: 1,
                    resource: wgpu::BindingResource::TextureView(&offscreen_texture.view),
                },
                wgpu::BindGroupEntry {
                    binding: 2,
                    resource: wgpu::BindingResource::TextureView(&compute_texture.view),
                },
            ],
            label: Some("compute_bind_group"),
        });

        let compute_shader_desc = wgpu::ShaderModuleDescriptor {
            label: Some("Compute Shader"),
            source: wgpu::ShaderSource::Wgsl(include_str!("..\\..\\shaders\\compute.wgsl").into()),
        };
        let compute_shader = device.create_shader_module(compute_shader_desc);
        let compute_pipeline_layout: wgpu::PipelineLayout =
            device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
                label: Some("Compute Pipeline Layout"),
                bind_group_layouts: &[&compute_bind_grp_layout],
                push_constant_ranges: &[],
            });
        let compute_pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
            layout: Some(&compute_pipeline_layout),
            module: &compute_shader,
            entry_point: Some("cs_main"),
            compilation_options: Default::default(),
            label: Some("Compute Shader Pipeline"),
            cache: None,
        });

        // present pipeline
        let present_vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
            label: Some("Present Vertex Buffer"),
            contents: bytemuck::cast_slice(QUAD_VERTICES),
            usage: wgpu::BufferUsages::VERTEX,
        });
        let present_index_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
            label: Some("Present Index Buffer"),
            contents: bytemuck::cast_slice(QUAD_INDICES),
            usage: wgpu::BufferUsages::INDEX,
        });
        let present_depth_texture =
            texture::Texture::create_depth_texture(&device, &config, "present_depth_texture");
        let present_bind_group_layout =
            device.create_bind_group_layout(&BindGroupLayoutDescriptor {
                label: Some("present_bind_group_layout"),
                entries: &[
                    wgpu::BindGroupLayoutEntry {
                        binding: 0,
                        visibility: wgpu::ShaderStages::FRAGMENT,
                        ty: wgpu::BindingType::Texture {
                            multisampled: false,
                            view_dimension: wgpu::TextureViewDimension::D2,
                            sample_type: wgpu::TextureSampleType::Float { filterable: true },
                        },
                        count: None,
                    },
                    wgpu::BindGroupLayoutEntry {
                        binding: 1,
                        visibility: wgpu::ShaderStages::FRAGMENT,
                        ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
                        count: None,
                    },
                ],
            });
        let present_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
            layout: &present_bind_group_layout,
            entries: &[
                wgpu::BindGroupEntry {
                    binding: 0,
                    resource: wgpu::BindingResource::TextureView(&compute_texture.view),
                },
                wgpu::BindGroupEntry {
                    binding: 1,
                    resource: wgpu::BindingResource::Sampler(&compute_texture.sampler),
                },
            ],
            label: Some("diffuse_bind_group"),
        });
        //
        let present_shader_desc = wgpu::ShaderModuleDescriptor {
            label: Some("Present Shader"),
            source: wgpu::ShaderSource::Wgsl(
                include_str!("..\\..\\shaders\\render_screen_quad.wgsl").into(),
            ),
        };
        let present_shader = device.create_shader_module(present_shader_desc);
        let present_render_pipeline_layout =
            device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
                label: Some("Present Pipeline Layout"),
                bind_group_layouts: &[&present_bind_group_layout],
                push_constant_ranges: &[],
            });
        let present_render_pipeline =
            device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
                label: Some("Present Pipeline"),
                layout: Some(&present_render_pipeline_layout),
                vertex: wgpu::VertexState {
                    module: &present_shader,
                    entry_point: Some("vs_main"),
                    buffers: &[ExampleVertex::desc()],
                    compilation_options: Default::default(),
                },
                fragment: Some(wgpu::FragmentState {
                    module: &present_shader,
                    entry_point: Some("fs_main"),
                    targets: &[Some(wgpu::ColorTargetState {
                        // format: config.format,
                        format: HdrPipeline::HDR_FORMAT,
                        blend: Some(wgpu::BlendState::REPLACE),
                        write_mask: wgpu::ColorWrites::ALL,
                    })],
                    compilation_options: Default::default(),
                }),
                primitive: wgpu::PrimitiveState {
                    topology: wgpu::PrimitiveTopology::TriangleList,
                    strip_index_format: None,
                    front_face: wgpu::FrontFace::Ccw,
                    cull_mode: Some(wgpu::Face::Back),
                    polygon_mode: wgpu::PolygonMode::Fill, // 如果将该字段设置为除了 Fill 之外的任何值，都需要 Features::NON_FILL_POLYGON_MODE
                    unclipped_depth: false,                // 需要 Features::DEPTH_CLIP_ENABLE
                    conservative: false, // 需要 Features::CONSERVATIVE_RASTERIZATION
                },
                depth_stencil: Some(wgpu::DepthStencilState {
                    format: texture::Texture::DEPTH_FORMAT,
                    depth_write_enabled: true,
                    depth_compare: wgpu::CompareFunction::Less,
                    stencil: wgpu::StencilState::default(),
                    bias: wgpu::DepthBiasState::default(),
                }),
                multisample: wgpu::MultisampleState {
                    count: 1,
                    mask: !0,
                    alpha_to_coverage_enabled: false,
                },
                multiview: None,
                cache: None,
            });

        // hdr
        let hdr_pipeline = HdrPipeline::new(&device, &config);
        // hdr env map
        let hdr_loader = HdrLoader::new(&device);
        // let sky_bytes = load_binary("xx.hdr");
        let sky_bytes = [];
        let sky_texture = hdr_loader
            .from_equirectangular_bytes(&device, &queue, &sky_bytes, 1080, Some("Sky Texture"))
            .unwrap();

        let environment_layout =
            device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
                label: Some("environment_layout"),
                entries: &[
                    wgpu::BindGroupLayoutEntry {
                        binding: 0,
                        visibility: wgpu::ShaderStages::FRAGMENT,
                        ty: wgpu::BindingType::Texture {
                            sample_type: wgpu::TextureSampleType::Float { filterable: false },
                            view_dimension: wgpu::TextureViewDimension::Cube,
                            multisampled: false,
                        },
                        count: None,
                    },
                    wgpu::BindGroupLayoutEntry {
                        binding: 1,
                        visibility: wgpu::ShaderStages::FRAGMENT,
                        ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::NonFiltering),
                        count: None,
                    },
                ],
            });

        let environment_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
            label: Some("environment_bind_group"),
            layout: &environment_layout,
            entries: &[
                wgpu::BindGroupEntry {
                    binding: 0,
                    resource: wgpu::BindingResource::TextureView(&sky_texture.view),
                },
                wgpu::BindGroupEntry {
                    binding: 1,
                    resource: wgpu::BindingResource::Sampler(&sky_texture.sampler),
                },
            ],
        });

        let sky_pipeline = {
            let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
                label: Some("Sky Pipeline Layout"),
                bind_group_layouts: &[&camera_bind_group_layout, &environment_layout],
                push_constant_ranges: &[],
            });
            let shader = wgpu::include_wgsl!("..//..//shaders//sky.wgsl");
            create_render_pipeline(
                &device,
                &layout,
                hdr_pipeline.format(),
                Some(texture::Texture::DEPTH_FORMAT),
                &[],
                wgpu::PrimitiveTopology::TriangleList,
                shader,
            )
        };

        //
        return Self {
            window: window,
            surface: surface,
            device: device,
            queue: queue,
            config: config,
            size: size,

            render_pipeline: render_pipeline,
            light_render_pipeline: light_render_pipeline,

            compute_pipeline: compute_pipeline,
            compute_bind_grp_layout: compute_bind_grp_layout,
            compute_bind_grp: compute_bind_grp,
            compute_texture: compute_texture,
            compute_uniform_buffer: compute_uniform_buffer,
            compute_uniform: compute_uniform,
            grid_size: grid_sz,
            workgroup_size: work_grp_sz,
            workgroup_count: work_grp_cnt,

            present_render_pipeline: present_render_pipeline,
            present_bind_group_layout: present_bind_group_layout,
            present_bind_group: present_bind_group,
            present_depth_texture: present_depth_texture,
            present_vertex_buffer: present_vertex_buffer,
            present_index_buffer: present_index_buffer,

            hdr_pipeline: hdr_pipeline,
            environment_bind_group: environment_bind_group,
            sky_pipeline: sky_pipeline,

            // vertex_buffer: vertex_buffer,
            // num_verts: num_verts,
            // index_buffer: index_buffer,
            // num_indices: num_indices,
            obj_model: obj_model,
            instances: instances,
            instance_buffer: instance_buffer,

            depth_texture: depth_texture,
            offscreen_texture: offscreen_texture,
            // diffuse_bind_group: diffuse_bind_group,
            // diffuse_texture: diffuse_texture,
            camera: camera,
            camera_uniform: camera_uniform,
            camera_buffer: camera_buffer,
            camera_bind_group: camera_bind_group,
            camera_controller: camera_controller,

            light_uniform: light_uniform,
            light_buffer: light_buffer,
            light_bind_group: light_bind_group,
        };
    }

    pub fn resize(&mut self, new_size: winit::dpi::PhysicalSize<u32>) {
        if new_size.width > 0 && new_size.height > 0 {
            //
            self.size = new_size;
            self.config.width = new_size.width;
            self.config.height = new_size.height;
            self.surface.configure(&self.device, &self.config);
            //
            self.depth_texture =
                texture::Texture::create_depth_texture(&self.device, &self.config, "depth_texture");
            self.offscreen_texture = texture::Texture::create_offscreen_texture(
                &self.device,
                "offscreen_texture",
                (new_size.width, new_size.height),
            );
            self.present_depth_texture = texture::Texture::create_depth_texture(
                &self.device,
                &self.config,
                "present_depth_texture",
            );
            //
            self.grid_size = (new_size.width, new_size.height, 1);
            self.workgroup_count = compute_workgroup_count(self.grid_size, self.workgroup_size);
            self.compute_uniform.img_size = [new_size.width, new_size.height];
            self.queue.write_buffer(
                &self.compute_uniform_buffer,
                0,
                bytemuck::cast_slice(&[self.compute_uniform]),
            );
            self.compute_texture = texture::Texture::create_compute_texture(
                &self.device,
                "compute_texture",
                (new_size.width, new_size.height),
            );
            self.compute_bind_grp = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
                layout: &self.compute_bind_grp_layout,
                entries: &[
                    wgpu::BindGroupEntry {
                        binding: 0,
                        resource: self.compute_uniform_buffer.as_entire_binding(),
                    },
                    wgpu::BindGroupEntry {
                        binding: 1,
                        resource: wgpu::BindingResource::TextureView(&self.offscreen_texture.view),
                    },
                    wgpu::BindGroupEntry {
                        binding: 2,
                        resource: wgpu::BindingResource::TextureView(&self.compute_texture.view),
                    },
                ],
                label: Some("compute_bind_group"),
            });
            //
            self.present_bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
                layout: &self.present_bind_group_layout,
                entries: &[
                    wgpu::BindGroupEntry {
                        binding: 0,
                        resource: wgpu::BindingResource::TextureView(&self.compute_texture.view),
                    },
                    wgpu::BindGroupEntry {
                        binding: 1,
                        resource: wgpu::BindingResource::Sampler(&self.compute_texture.sampler),
                    },
                ],
                label: Some("diffuse_bind_group"),
            });
            //
            self.hdr_pipeline
                .resize(&self.device, new_size.width, new_size.height);
        }
    }

    pub fn input_keyboard_event(
        &mut self,
        keycode: &KeyCode,
        element_state: &ElementState,
    ) -> bool {
        let kb_process_rst = self
            .camera_controller
            .process_keycode(keycode, element_state);
        return kb_process_rst;
    }

    pub fn update(&mut self) {
        // update camera
        self.camera_controller.update_camera(&mut self.camera);
        self.camera_uniform.update_view_proj(&self.camera);
        self.queue.write_buffer(
            &self.camera_buffer,
            0,
            bytemuck::cast_slice(&[self.camera_uniform]),
        );

        // update light
        let old_position = glam::Vec3::from_array(self.light_uniform.position);
        self.light_uniform.position =
            (glam::Quat::from_axis_angle(glam::Vec3::Y, f32::consts::PI / 180.) * old_position)
                .into();
        self.queue.write_buffer(
            &self.light_buffer,
            0,
            bytemuck::cast_slice(&[self.light_uniform]),
        );
    }

    pub fn render(&mut self) -> Result<(), wgpu::SurfaceError> {
        // begin_frame
        let tex = self.surface.get_current_texture()?;
        let view = tex.texture.create_view(&TextureViewDescriptor::default());
        let mut cmd_encoder = self
            .device
            .create_command_encoder(&CommandEncoderDescriptor {
                label: Some("render encoder"),
            });

        // begin_render_pass
        {
            let mut render_pass = cmd_encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
                label: Some("render pass"),
                color_attachments: &[Some(RenderPassColorAttachment {
                    // view: &view,
                    view: &self.offscreen_texture.view,
                    resolve_target: None,
                    ops: Operations {
                        load: wgpu::LoadOp::Clear(Color {
                            r: 0.1,
                            g: 0.1,
                            b: 0.1,
                            a: 1.0,
                        }),
                        store: wgpu::StoreOp::Store,
                    },
                })],
                depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
                    view: &self.depth_texture.view,
                    depth_ops: Some(wgpu::Operations {
                        load: wgpu::LoadOp::Clear(1.0),
                        store: wgpu::StoreOp::Store,
                    }),
                    stencil_ops: None,
                }),
                // timestamp_writes: ,
                // occlusion_query_set: ,
                ..Default::default()
            });

            render_pass.set_vertex_buffer(1, self.instance_buffer.slice(..));
            render_pass.set_pipeline(&self.light_render_pipeline);
            render_pass.draw_light_model(
                &self.obj_model,
                &self.camera_bind_group,
                &self.light_bind_group,
            );
            render_pass.set_pipeline(&self.render_pipeline);
            render_pass.draw_model_instanced(
                &self.obj_model,
                0..self.instances.len() as u32,
                &self.camera_bind_group,
                &self.light_bind_group,
            );
            // render_pass.set_bind_group(0, &self.diffuse_bind_group, &[]);
            // render_pass.set_bind_group(1, &self.camera_bind_group, &[]);
            // render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
            // render_pass.set_index_buffer(self.index_buffer.slice(..), wgpu::IndexFormat::Uint16);
            // render_pass.draw(0..self.num_verts, 0..1);
            // render_pass.draw_indexed(0..self.num_indices, 0, 0..self.instances.len() as _);
            // render_pass.draw_mesh_instanced(&self.obj_model.meshes[0], &self.obj_model.materials[self.obj_model.meshes[0].material], 0..self.instances.len() as u32, &self.camera_bind_group);
        }

        {
            let mut cpass = cmd_encoder.begin_compute_pass(&ComputePassDescriptor {
                label: Some("Compute Pass"),
                timestamp_writes: None,
            });
            cpass.set_pipeline(&self.compute_pipeline);
            cpass.set_bind_group(0, &self.compute_bind_grp, &[]);
            cpass.dispatch_workgroups(self.workgroup_count.0, self.workgroup_count.1, 1);
        }

        {
            // cmd_encoder.copy_texture_to_texture(source, destination, copy_size);
            let mut present_render_pass =
                cmd_encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
                    label: Some("present render pass"),
                    color_attachments: &[Some(RenderPassColorAttachment {
                        view: self.hdr_pipeline.view(),
                        resolve_target: None,
                        ops: Operations {
                            load: wgpu::LoadOp::Clear(Color {
                                r: 0.1,
                                g: 1.0,
                                b: 0.1,
                                a: 1.0,
                            }),
                            store: wgpu::StoreOp::Store,
                        },
                    })],
                    depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
                        view: &self.present_depth_texture.view,
                        depth_ops: Some(wgpu::Operations {
                            load: wgpu::LoadOp::Clear(1.0),
                            store: wgpu::StoreOp::Store,
                        }),
                        stencil_ops: None,
                    }),
                    ..Default::default()
                });
            present_render_pass.set_pipeline(&self.present_render_pipeline);
            present_render_pass.set_bind_group(0, &self.present_bind_group, &[]);
            present_render_pass.set_vertex_buffer(0, self.present_vertex_buffer.slice(..));
            present_render_pass.set_index_buffer(
                self.present_index_buffer.slice(..),
                wgpu::IndexFormat::Uint16,
            );
            // present_render_pass.draw(0..6, 0..1);
            present_render_pass.draw_indexed(0..QUAD_INDICES.len() as u32, 0, 0..1);
        }

        {
            self.hdr_pipeline.process(&mut cmd_encoder, &view);
        }

        // end_frame
        let cmd_buf = cmd_encoder.finish();
        self.window.pre_present_notify();
        self.queue.submit(std::iter::once(cmd_buf));
        tex.present();
        Ok(())
    }
}
